package com.pphh.demo;

import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.InputStream;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Properties;
import java.util.Random;

/**
 * a java crypto util, which load settings from resource file - application.properties
 *
 * @author huangyinhuang
 * @date 7/27/2018
 */
public class JavaCryptoUtil {

    public static final String AES_ALGORITHM = "AES";
    public static final String CBC_CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
    private static JavaCryptoUtil ourInstance = new JavaCryptoUtil();
    private Integer secretKeySize = 128;
    private Cipher encryptCiper = null;
    private Cipher decryptCiper = null;

    private JavaCryptoUtil() {
    }

    public static JavaCryptoUtil getInstance() throws Exception {
        if (!ourInstance.hasInitialized()) {
            Properties prop = new Properties();
            InputStream in = JavaCryptoUtil.class.getClassLoader().getResourceAsStream("application.properties");
            if (in == null) {
                throw new Exception("failed to load application properties on the resource directory");
            }
            prop.load(in);
            in.close();

            String secretKey = prop.getProperty("app.crypto.secret.key");
            String secretIV = prop.getProperty("app.crypto.secret.iv");

            ourInstance.init(secretKey, secretIV);
        }
        return ourInstance;
    }

    public SecretKey decodeKey(byte[] keyBase64) {
        Base64.Decoder decoder = Base64.getDecoder();
        return new SecretKeySpec(decoder.decode(keyBase64), AES_ALGORITHM);
    }

    public byte[] encodeKey(SecretKey key) {
        Base64.Encoder encoder = Base64.getEncoder();
        return encoder.encode(key.getEncoded());
    }

    public SecretKey generateAESSecretKey(int keySize) {
        SecretKey key = null;
        try {
            KeyGenerator keyGenerator = KeyGenerator.getInstance(AES_ALGORITHM);
            keyGenerator.init(keySize);
            key = keyGenerator.generateKey();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return key;
    }

    public byte[] generateSecretIV(int keySize) {
        byte[] iv = new byte[keySize / 8];
        Random random = new Random();
        random.nextBytes(iv);
        return Base64.getEncoder().encode(iv);
    }

    public byte[] encrypt(byte[] plainText) throws BadPaddingException, IllegalBlockSizeException {
        if (this.encryptCiper == null) {
            throw new NullPointerException("The encryptCiper has NOT been initialized. " +
                    "Please provide the secret key to initialize it before text encryption.");
        }
        return this.encryptCiper.doFinal(plainText);
    }

    public byte[] decrypt(byte[] secretText) throws BadPaddingException, IllegalBlockSizeException {
        if (this.decryptCiper == null) {
            throw new NullPointerException("The decryptCiper has NOT been initialized. " +
                    "Please provide the secret key to initialize it before text decryption.");
        }
        return this.decryptCiper.doFinal(secretText);
    }

    public void init(String secretKey, String secretIV) throws Exception {
        byte[] ivParameter = Base64.getDecoder().decode(secretIV.getBytes());
        this.secretKeySize = ivParameter.length * 8;
        IvParameterSpec ivParameterSpec = new IvParameterSpec(ivParameter);

        if (secretKey != null) {
            SecretKey key = decodeKey(secretKey.getBytes());
            this.encryptCiper = Cipher.getInstance(CBC_CIPHER_ALGORITHM);
            this.encryptCiper.init(Cipher.ENCRYPT_MODE, key, ivParameterSpec);

            this.decryptCiper = Cipher.getInstance(CBC_CIPHER_ALGORITHM);
            this.decryptCiper.init(Cipher.DECRYPT_MODE, key, ivParameterSpec);
        }
    }

    public Boolean hasInitialized() {
        return this.decryptCiper != null && this.encryptCiper != null;
    }
}
